﻿using System;
using System.Collections.Generic;
using System.Text;
using DSDecmp.Formats.Nitro;
using DSDecmp.Formats;
using System.IO;
using System.Xml.Linq;
using Ekona;

namespace DSDecmp
{
    public static class Main
    {
        public static FormatCompress Get_Format(string input)
        {
            CompressionFormat fmt = null;

            foreach (FormatCompress f in Enum.GetValues(typeof(FormatCompress)))
            {
                switch (f)
                {
                    //case FormatCompress.LZOVL: fmt = new LZOvl(); break;
                    case FormatCompress.LZ10: fmt = new LZ10(); break;
                    case FormatCompress.LZ11: fmt = new LZ11(); break;
                    case FormatCompress.RLE: fmt = new RLE(); break;
                    case FormatCompress.HUFF: fmt = new Huffman(); break;
                }

                if (fmt == null)
                    continue;

                if (fmt.Supports(input))
                    return f;
            }

            return FormatCompress.Invalid;
        }
        public static FormatCompress Get_Format(FileStream input, bool arm9)
        {
            CompressionFormat fmt = null;

            foreach (FormatCompress f in Enum.GetValues(typeof(FormatCompress)))
            {
                switch (f)
                {
                    //case FormatCompress.LZOVL: fmt = new LZOvl(); break;
                    case FormatCompress.LZ10: fmt = new LZ10(); break;
                    case FormatCompress.LZ11: fmt = new LZ11(); break;
                    case FormatCompress.RLE: fmt = new RLE(); break;
                    case FormatCompress.HUFF: fmt = new Huffman(); break;
                }

                if (fmt == null)
                    continue;

                long fLength = input.Length;
                if (arm9)
                    fLength -= 0xC;

                if (fmt.Supports(input, fLength))
                    return f;
            }

            return FormatCompress.Invalid;
        }

        #region compression methods
        public static void Compress(string input, string output, FormatCompress format)
        {
            // compress the input
            MemoryStream compressedData = new MemoryStream();
            FormatCompress compressedFormat;
            int outsize = DoCompress(input, compressedData, format, out compressedFormat);
            if (outsize < 0)
                return;

            using (FileStream outStream = File.Create(output))
            {
                compressedData.WriteTo(outStream);
                Console.WriteLine(String.Format(Main.Get_Traduction("S19"), compressedFormat.ToString(), input, output));
            }
        }

        public static int DoCompress(string infile, MemoryStream output, FormatCompress format, out FormatCompress actualFormat)
        {
            CompressionFormat fmt = null;
            switch (format)
            {
                case FormatCompress.LZ10: fmt = new LZ10(); break;
                case FormatCompress.LZ11: fmt = new LZ11(); break;
                case FormatCompress.LZOVL: fmt = new LZOvl(); break;
                case FormatCompress.RLE: fmt = new RLE(); break;
                case FormatCompress.HUFF4: Huffman.CompressBlockSize = Huffman.BlockSize.FOURBIT; fmt = new Huffman(); break;
                case FormatCompress.HUFF8: Huffman.CompressBlockSize = Huffman.BlockSize.EIGHTBIT; fmt = new Huffman(); break;
                case FormatCompress.HUFF:
                    return CompressHuff(infile, output, out actualFormat);
                case FormatCompress.GBA:
                    return CompressGBA(infile, output, out actualFormat);
                case FormatCompress.NDS:
                    return CompressNDS(infile, output, out actualFormat);
                default:
                    actualFormat = FormatCompress.Invalid;
                    return -1;
            }
            actualFormat = format;

            using (FileStream inStream = File.OpenRead(infile))
            {
                try
                {
                    return fmt.Compress(inStream, inStream.Length, output);
                }
                catch (Exception s)
                {
                    // any exception generated by compression is a fatal exception
                    Console.WriteLine(s.Message);
                    return -1;
                }
            }
        }

        private static int CompressHuff(string infile, MemoryStream output, out FormatCompress actualFormat)
        {
            return CompressBest(infile, output, out actualFormat, FormatCompress.HUFF4, FormatCompress.HUFF8);
        }
        private static int CompressGBA(string infile, MemoryStream output, out FormatCompress actualFormat)
        {
            return CompressBest(infile, output, out actualFormat, FormatCompress.HUFF4, FormatCompress.HUFF8, FormatCompress.LZ10, FormatCompress.RLE);
        }
        private static int CompressNDS(string infile, MemoryStream output, out FormatCompress actualFormat)
        {
            return CompressBest(infile, output, out actualFormat, FormatCompress.HUFF4, FormatCompress.HUFF8, FormatCompress.LZ10, FormatCompress.LZ11, FormatCompress.RLE);
        }

        private static int CompressBest(string infile, MemoryStream output, out FormatCompress actualFormat, params FormatCompress[] formats)
        {
            // only read the input data once from the file.
            byte[] inputData;
            using (FileStream inStream = File.OpenRead(infile))
            {
                inputData = new byte[inStream.Length];
                inStream.Read(inputData, 0, inputData.Length);
            }

            MemoryStream bestOutput = null;
            int minCompSize = int.MaxValue;
            actualFormat = FormatCompress.GBA;
            foreach (FormatCompress format in formats)
            {
                #region compress the file in each format, and save the best one

                MemoryStream currentOutput = new MemoryStream();
                CompressionFormat realFormat = null;
                switch (format)
                {
                    case FormatCompress.HUFF4: Huffman.CompressBlockSize = Huffman.BlockSize.FOURBIT; realFormat = new Huffman(); break;
                    case FormatCompress.HUFF8: Huffman.CompressBlockSize = Huffman.BlockSize.EIGHTBIT; realFormat = new Huffman(); break;
                    case FormatCompress.LZ10: realFormat = new LZ10(); break;
                    case FormatCompress.LZ11: realFormat = new LZ11(); break;
                    case FormatCompress.LZOVL: realFormat = new LZOvl(); break;
                    case FormatCompress.RLE: realFormat = new RLE(); break;
                }

                int currentOutSize;
                try
                {
                    using (MemoryStream inStream = new MemoryStream(inputData))
                    {
                        currentOutSize = realFormat.Compress(inStream, inStream.Length, currentOutput);
                    }
                }
                catch (InputTooLargeException i)
                {
                    Console.WriteLine(i.Message);
                    actualFormat = format;
                    return -1;
                }
                catch (Exception)
                {
                    continue;
                }
                if (currentOutSize < minCompSize)
                {
                    bestOutput = currentOutput;
                    minCompSize = currentOutSize;
                    actualFormat = format;
                }

                #endregion
            }

            if (bestOutput == null)
            {
                Console.WriteLine(Main.Get_Traduction("S1A"));
                return -1;
            }
            bestOutput.WriteTo(output);
            return minCompSize;
        }
        #endregion

        #region decompression methods
        public static void Decompress(string input, string output)
        {
            byte[] inData;
            using (FileStream inStream = File.OpenRead(input))
            {
                inData = new byte[inStream.Length];
                inStream.Read(inData, 0, inData.Length);
            }

            MemoryStream decompressedData = new MemoryStream();
            long decSize = -1;
            FormatCompress usedFormat = FormatCompress.NDS;
            // just try all formats, and stop once one has been found that can decompress it.
            foreach (FormatCompress f in Enum.GetValues(typeof(FormatCompress)))
            {
                using (MemoryStream inStream = new MemoryStream(inData))
                {
                    decSize = Decompress(inStream, decompressedData, f);
                    if (decSize >= 0)
                    {
                        usedFormat = f;
                        break;
                    }
                }
            }
            if (decSize < 0)
            {
                Console.WriteLine(String.Format(Main.Get_Traduction("S1B"), input));
                return;
            }

            byte[] outData = decompressedData.ToArray();
            using (FileStream outStream = File.Create(output))
            {
                outStream.Write(outData, 0, outData.Length);
                Console.WriteLine(String.Format(Main.Get_Traduction("S1C"), usedFormat.ToString(), input, output));
            }

        }
        public static void Decompress(string input, string output, FormatCompress format)
        {
            byte[] inData;
            using (FileStream inStream = File.OpenRead(input))
            {
                inData = new byte[inStream.Length];
                inStream.Read(inData, 0, inData.Length);
            }

            MemoryStream decompressedData = new MemoryStream();
            long decSize = -1;
            // just try all formats, and stop once one has been found that can decompress it.
            using (MemoryStream inStream = new MemoryStream(inData))
            {
                decSize = Decompress(inStream, decompressedData, format);
            }
            if (decSize < 0)
            {
                Console.WriteLine(String.Format(Main.Get_Traduction("S1B"), input));
                return;
            }

            byte[] outData = decompressedData.ToArray();
            using (FileStream outStream = File.Create(output))
            {
                outStream.Write(outData, 0, outData.Length);
                Console.WriteLine(String.Format(Main.Get_Traduction("S1C"), format.ToString(), input, output));
            }

        }
        private static long Decompress(MemoryStream inputStream, MemoryStream output, FormatCompress format)
        {
            CompressionFormat realFormat = null;
            switch (format)
            {
                case FormatCompress.HUFF:
                    realFormat = new Huffman(); break;
                case FormatCompress.LZ10:
                    realFormat = new LZ10(); break;
                case FormatCompress.LZ11:
                    realFormat = new LZ11(); break;
                case FormatCompress.LZOVL:
                    realFormat = new LZOvl(); break;
                case FormatCompress.RLE:
                    realFormat = new RLE(); break;
                default:
                    return -1;
            }
            if (!realFormat.Supports(inputStream, inputStream.Length))
                return -1;
            try
            {
                return realFormat.Decompress(inputStream, inputStream.Length, output);
            }
            catch (TooMuchInputException e)
            {
                Console.WriteLine(e.Message);
                return output.Length;
            }
            catch (Exception e)
            {
                Console.WriteLine(String.Format(Main.Get_Traduction("S1D"), format.ToString(), e.Message));
                return -1;
            }
        }
        #endregion

        internal static string Get_Traduction(string code)
        {
            String message = "";
            try
            {
                XElement xml = XElement.Load(System.Windows.Forms.Application.StartupPath + Path.DirectorySeparatorChar + "Tinke.xml");
                string idioma = xml.Element("Options").Element("Language").Value;
                xml = null;

                foreach (string langFile in Directory.GetFiles(System.Windows.Forms.Application.StartupPath + Path.DirectorySeparatorChar + "langs"))
                {
                    if (!langFile.EndsWith(".xml"))
                        continue;

                    xml = XElement.Load(langFile);
                    if (xml.Attribute("name").Value == idioma)
                        break;
                }

                message = xml.Element("DSDecmp").Element(code).Value;
            }
            catch { throw new Exception("There was an error in the XML file of language."); }

            return message;
        }
        internal static XElement Get_Traduction()
        {
            XElement tree = null;
            try
            {
                XElement xml = XElement.Load(System.Windows.Forms.Application.StartupPath + Path.DirectorySeparatorChar + "Tinke.xml");
                string idioma = xml.Element("Options").Element("Language").Value;
                xml = null;

                foreach (string langFile in Directory.GetFiles(System.Windows.Forms.Application.StartupPath + Path.DirectorySeparatorChar + "langs"))
                {
                    if (!langFile.EndsWith(".xml"))
                        continue;

                    xml = XElement.Load(langFile);
                    if (xml.Attribute("name").Value == idioma)
                        break;
                }

                tree = xml.Element("DSDecmp");
            }
            catch { throw new Exception("There was an error in the XML file of language."); }

            return tree;
        }
    }
}
